﻿// <copyright file="KinectOverTrigger.cs" company="Ucaya">
// </copyright>
// <author>Ucaya</author>

namespace KinectExtensions.Interactivity
{
    #region

    using System;
    using System.ComponentModel;
    using System.ComponentModel.Composition;
    using System.Windows;
    using System.Windows.Interactivity;

    using KinectExtensions.Nui;
    using KinectExtensions.Services;

    using DesignerProperties = KinectExtensions.DesignerProperties;

    #endregion

    /// <summary>
    ///   TODO: Update summary.
    /// </summary>
    public class KinectOverTrigger : TriggerBase<FrameworkElement>
    {
        #region Constants and Fields

        /// <summary>
        ///   IsKinectOver depedency property.
        /// </summary>
        public static readonly DependencyProperty IsKinectOverProperty = DependencyProperty.Register("IsKinectOver", typeof(bool), typeof(KinectOverTrigger), new PropertyMetadata((d, e) => ((KinectOverTrigger)d).OnIsKinectOverChanged((bool)e.OldValue, (bool)e.NewValue)));

        /// <summary>
        ///   KinectNormalStateName depedency property.
        /// </summary>
        public static readonly DependencyProperty KinectNormalStateNameProperty = DependencyProperty.Register("KinectNormalStateName", typeof(string), typeof(KinectOverTrigger), new PropertyMetadata((d, e) => ((KinectOverTrigger)d).OnKinectNormalStateNameChanged((string)e.OldValue, (string)e.NewValue)));

        /// <summary>
        ///   KinectOverStateName depedency property.
        /// </summary>
        public static readonly DependencyProperty KinectOverStateNameProperty = DependencyProperty.Register("KinectOverStateName", typeof(string), typeof(KinectOverTrigger), new PropertyMetadata((d, e) => ((KinectOverTrigger)d).OnKinectOverStateNameChanged((string)e.OldValue, (string)e.NewValue)));

        /// <summary>
        ///   OverPosition depedency property.
        /// </summary>
        public static readonly DependencyProperty OverPositionProperty = DependencyProperty.Register("OverPosition", typeof(Point), typeof(KinectOverTrigger), new PropertyMetadata((d, e) => ((KinectOverTrigger)d).OnOverPositionChanged((Point)e.OldValue, (Point)e.NewValue)));

        /// <summary>
        ///   The hit test service listener.
        /// </summary>
        private readonly HitTestServiceListener hitTestServiceListener;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "KinectOverTrigger" /> class.
        /// </summary>
        public KinectOverTrigger()
        {
            if (DesignerProperties.IsInDesignTool)
            {
                return;
            }

            ServiceLocator.Instance.SatisfyImportsOnce(this);
            this.hitTestServiceListener = new HitTestServiceListener(this, this.HitTestService);
        }

        /// <summary>
        ///   Finalizes an instance of the <see cref = "KinectOverTrigger" /> class.
        /// </summary>
        ~KinectOverTrigger()
        {
            if (this.hitTestServiceListener != null)
            {
                this.hitTestServiceListener.Dispose();
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets or sets HitTestService.
        /// </summary>
        [Import]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public IHitTestService HitTestService { get; set; }

        /// <summary>
        ///   Gets or sets a value indicating whether IsKinectOver.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool IsKinectOver
        {
            get
            {
                return (bool)this.GetValue(IsKinectOverProperty);
            }

            set
            {
                this.SetValue(IsKinectOverProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets KinectNormalStateName.
        /// </summary>
        public string KinectNormalStateName
        {
            get
            {
                return (string)this.GetValue(KinectNormalStateNameProperty);
            }

            set
            {
                this.SetValue(KinectNormalStateNameProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets KinectOverStateName.
        /// </summary>
        public string KinectOverStateName
        {
            get
            {
                return (string)this.GetValue(KinectOverStateNameProperty);
            }

            set
            {
                this.SetValue(KinectOverStateNameProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets OverPosition.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public Point OverPosition
        {
            get
            {
                return (Point)this.GetValue(OverPositionProperty);
            }

            set
            {
                this.SetValue(OverPositionProperty, value);
            }
        }

        #endregion

        #region Methods

        protected virtual void HitTestAssodiatedObject(out bool isOver, out Point overPosition)
        {
            if (this.AssociatedObject != null)
            {
                HitResult hitResult = this.HitTestService.HitTestHandMaster(this.AssociatedObject);
                isOver = hitResult.Result;
                overPosition = new Point(hitResult.RelativeTouchPosition.X, hitResult.RelativeTouchPosition.Y);
            }
            else
            {
                isOver = false;
                overPosition = new Point();
            }
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.Unloaded += this.AssociatedObject_Unloaded;
        }

        /// <summary>
        ///   handles the IsKinectOverProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        protected virtual void OnIsKinectOverChanged(bool oldValue, bool newValue)
        {
            this.RefreshOverState();
        }

        /// <summary>
        ///   handles the OverPositionProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        protected virtual void OnOverPositionChanged(Point oldValue, Point newValue)
        {
        }

        private void AssociatedObject_Unloaded(object sender, RoutedEventArgs e)
        {
        }

        /// <summary>
        ///   handles the KinectNormalStateNameProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnKinectNormalStateNameChanged(string oldValue, string newValue)
        {
            this.RefreshOverState();
        }

        /// <summary>
        ///   handles the KinectOverStateNameProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnKinectOverStateNameChanged(string oldValue, string newValue)
        {
            this.RefreshOverState();
        }

        /// <summary>
        ///   The refresh over state.
        /// </summary>
        private void RefreshOverState()
        {
            if (this.AssociatedObject != null)
            {
                if (this.IsKinectOver)
                {
                    if (!string.IsNullOrEmpty(this.KinectOverStateName))
                    {
                        VisualStateManager.GoToState(this.AssociatedObject, this.KinectOverStateName, true);
                    }
                }
                else
                {
                    if (!string.IsNullOrEmpty(this.KinectNormalStateName))
                    {
                        VisualStateManager.GoToState(this.AssociatedObject, this.KinectNormalStateName, true);
                    }
                }
            }
        }

        /// <summary>
        ///   The hit test service_ hit may have changed.
        /// </summary>
        /// <param name = "sender">
        ///   The sender.
        /// </param>
        /// <param name = "e">
        ///   The e.
        /// </param>
        private void hitTestService_HitMayHaveChanged(object sender, EventArgs e)
        {
            bool isOver;
            Point overPosition;
            this.HitTestAssodiatedObject(out isOver, out overPosition);
            this.OverPosition = overPosition;
            this.IsKinectOver = isOver;
        }

        #endregion

        /// <summary>
        ///   The hit test service listener.
        /// </summary>
        private class HitTestServiceListener : IDisposable
        {
            #region Constants and Fields

            /// <summary>
            ///   The weak trigger.
            /// </summary>
            private readonly WeakReference weakTrigger;

            /// <summary>
            ///   The hit test service.
            /// </summary>
            private IHitTestService hitTestService;

            #endregion

            #region Constructors and Destructors

            /// <summary>
            ///   Initializes a new instance of the <see cref = "HitTestServiceListener" /> class.
            /// </summary>
            /// <param name = "trigger">
            ///   The trigger.
            /// </param>
            /// <param name = "hitTestService">
            ///   The hit test service.
            /// </param>
            public HitTestServiceListener(KinectOverTrigger trigger, IHitTestService hitTestService)
            {
                this.hitTestService = hitTestService;
                this.weakTrigger = new WeakReference(trigger);
                this.hitTestService.HitMayHaveChanged += this.hitTestService_HitMayHaveChanged;
            }

            #endregion

            #region Public Methods

            /// <summary>
            ///   The dispose.
            /// </summary>
            public void Dispose()
            {
                if (this.hitTestService != null)
                {
                    try
                    {
                        this.hitTestService.HitMayHaveChanged -= this.hitTestService_HitMayHaveChanged;
                        this.hitTestService = null;
                    }
                    catch
                    {
                    }
                }
            }

            #endregion

            #region Methods

            /// <summary>
            ///   The hit test service_ hit may have changed.
            /// </summary>
            /// <param name = "sender">
            ///   The sender.
            /// </param>
            /// <param name = "e">
            ///   The e.
            /// </param>
            private void hitTestService_HitMayHaveChanged(object sender, EventArgs e)
            {
                var trigger = this.weakTrigger.Target as KinectOverTrigger;
                if (trigger == null)
                {
                    this.Dispose();
                }
                else
                {
                    trigger.hitTestService_HitMayHaveChanged(sender, e);
                }
            }

            #endregion
        }
    }
}